Skip to content

S11-12 Vue-项目:mr-vue3-ts-consult-patient

[TOC]

环境搭建

接口文档

地址:https://apifox.com/apidoc/shared-aeb0d03e-c713-4f55-afaf-21cddf542751/api-160608919

技术栈

  • Vue3:@3.5.13
  • TS5:@5.6.3
  • Vite6:@6.0.5。使用create-vue创建项目
  • Pinia2:@2.3.0
  • VueRouter4: @4.5.0
  • Node20:node@20.11.1
  • Vant:

创建项目

使用create-vue 工具创建mr-vue3-ts-consult-patient项目。create-vue 是基于vite的脚手架工具

sh
pnpm create vue@latest

创建选项

image-20250104102258763

项目配置

Git配置

image-20250104102747336

Eslint配置

1、在.eslintrc.cjs中配置prettier代码风格

js
  rules: {
    'prettier/prettier': [
      'warn',
      {
        singleQuote: true,
        semi: false,
        printWidth: 80,
        trailingComma: 'none',
        endOfLine: 'auto'
      }
    ],
    // 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
    'no-undef': 'error'
  }

2、忽略vue组件多单词警告

js
  rules: {
    'vue/multi-word-component-names': [
      'warn',
      {
        ignores: ['index']
      }
    ],
  }

3、关闭props解构警告

js
  rules: {
    'vue/no-setup-props-destructure': ['off'],
  }

后面开启响应式语法糖就结构props就不会再丢失响应式。

image-20250303143725326

Husky配置

1、初始化与安装

sh
pnpm dlx husky-init && pnpm install

2、修改 .husky/pre-commit 文件

sh
pnpm lint

lint-staged配置

1、安装

sh
pnpm i lint-staged -D

2、配置 package.json

sh
{
  "scripts": {
    "lint-staged": "lint-staged"
  }
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix"
    ]
  }
}

3、修改 .husky/pre-commit 文件

sh
pnpm lint-staged

目录结构

每一个目录结构的作用:

sh
./src
├── assets        `静态资源,图片...`
├── components    `通用组件`
├── hook    `组合功能通用函数`
├── icon         `svg图标`
├── router        `路由`
   └── index.ts
├── service      `接口服务API`
├── store        `状态仓库`
├── style        `样式`
   └── main.scss
├── type         `TS类型`
├── utils         `工具函数`
├── views         `页面`
├── main.ts       `入口文件`
└──App.vue       `根组件`

集成-Vant

基本导入

1、安装 vant

sh
pnpm i vant

2、在main.ts中引入样式

ts
import 'vant/lib/index.css'

3、在组件中使用vant组件

image-20250303163533930

4、推荐按需引入

组件自动注册

已过时

替代方法:使用vant的按需导入

痛点:使用手动导入组件的方法过于繁琐,每次使用时都需要按以下方法手动导入。

image-20250310111023740

解决:使用 unplugin-vue-components 实现自动按需加载,和自动导入组件。

依赖包unplugin-vue-components

1、安装 unplugin-vue-components 插件

sh
pnpm i unplugin-vue-components -D

2、配置 vite.config.ts

image-20250310111633238

3、优化:样式重复的优化。

问题:配置后会出现vant组件的样式重复。

image-20250310111826916

原因分析:这是因为在main.ts中导入的vant样式和自动注册组件时导入的vant样式重复了,导入了2次样式。

解决:设置组件自动导入的配置,让自动注册时不要导入样式。

image-20250310112230549

4、优化:类型声明文件重复的优化。(新版待确定)

问题components.d.ts的类型声明文件时多余的,vant本身自带了类型声明。

解决:设置组件自动导入的配置,不生成类型声明文件 components.d.ts

image-20250310112557048

5、注意:安装了插件后,components目录下的组件也会自动注册,不需要再手动导入。

移动端适配

1、安装 postcss-px-to-viewport

sh
pnpm i postcss-px-to-viewport -D

2、配置 postcss.config.js

js
// postcss.config.js
module.exports = {
  plugins: {
    'postcss-px-to-viewport': {
      viewportWidth: 375,
    },
  },
};

3、重启项目生效

image-20250303164807721

主题定制

使用css变量定制项目主题,和修改vant主题

CSS变量定义/使用

scss
:root {
  --main-color: #999; /** 定义CSS全局变量 */
}
.footer {
  --footer-color: #f0f; /** 定义CSS局部变量 */
}
a {
  color: var(--main-color)  /** 使用CSS变量 */
}

项目主题

scss
:root {
  // 问诊患者:色板
  --cp-primary: #16C2A3;
  --cp-plain: #EAF8F6;
  --cp-orange: #FCA21C;
  --cp-text1: #121826;
  --cp-text2: #3C3E42;
  --cp-text3: #6F6F6F;
  --cp-tag: #848484;
  --cp-dark: #979797;
  --cp-tip: #C3C3C5;
  --cp-disable: #D9DBDE;
  --cp-line: #EDEDED;
  --cp-bg: #F6F7F9;
  --cp-price: #EB5757;
    
  // 覆盖vant主题色
  --van-primary-color: var(--cp-primary);
}

image-20250303165557241

集成-Store

useUserStore

TS类型

type/user.d.ts中定义User的TS类型

ts
// 用户信息
export type User = {
  /** token令牌 */
  token: string
  /** 用户ID */
  id: string
  /** 用户名称 */
  account: string
  /** 手机号 */
  mobile: string
  /** 头像 */
  avatar: string
}
创建Store

1、在store/user.ts中创建useUserStore

ts
import type { User } from '@/types/user'
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useUserStore = defineStore('cp-user', () => {
  // 用户信息
  const user = ref<User>()
  // 设置用户,登录后使用
  const setUser = (u: User) => {
    user.value = u
  }
  // 清空用户,退出后使用
  const delUser = () => {
    user.value = undefined
  }
  return { user, setUser, delUser }
})

2、在组件中设置/删除user

image-20250303170715051

Store持久化

思路:使用 pinia-plugin-persistedstate 实现pinia仓库状态持久化

1、安装 pinia-plugin-persistedstate 插件

sh
pnpm i pinia-plugin-persistedstate

2、在main.ts中使用插件

image-20250303171030354

3、在 store/user.ts中配置本地持久化

image-20250303171125778

4、开启后store中的数据会被存储在localStorage中

image-20250303171320711

统一管理

实现仓库统一从 store/index.ts 导出,代码简洁,职能单一,入口唯一

抽取实例

1、抽取pinia实例代码到 store/index.ts

ts
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

// 1. 创建pinia实例
const pinia = createPinia()
// 2. 使用pinia插件
pinia.use(persist)
// 3. 导出pinia实例,给main使用
export default pinia

2、在 main.ts 中挂载pinia实例

ts
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './stores'
import router from './router'
import './styles/main.scss'

const app = createApp(App)

app.use(pinia)
app.use(router)
app.mount('#app')
统一导出模块

1、在store/index.ts 中统一导出Store模块

ts
export * from './modules/user'

2、在组件中使用导出的模块

ts
-import { useUserStore } from './stores/user'
+import { useUserStore } from './stores'

集成-axios

1、安装axios

sh
pnpm i axios

2、baseURL,timeout

image-20250303173404157

3、携带token

image-20250303173332096

4、验证携带token

image-20250303173422674

响应成功,业务失败处理

业务失败:响应数据返回的code不是10000(项目后端自定义的规则)。

失败处理

  1. 弹出轻提示
  2. 此时返回一个失败的promise,传递code给catch,以便后续根据code进行不同的处理

1、在axios响应拦截器中处理业务失败

image-20250303174229079

2、测试:登录失败

image-20250303174359526

返回核心数据

需求:业务逻辑成功,返回响应数据data,后续直接使用

image-20250303174628582

代码实现

image-20250303174715371

401错误处理

401错误:token失效。

401错误处理

  1. 删除用户信息
  2. 跳转登录,带上接口失效所在页面的地址,登录完成后回跳使用
ts
// 3. 响应拦截器,剥离无效数据,401拦截
instance.interceptors.response.use(
  (res) => {
    // 后台约定,响应成功,但是code不是10000,是业务逻辑失败
    if (res.data?.code !== 10000) {
      showToast(res.data?.message || '业务失败')
      return Promise.reject(res.data)
    }
    // 业务逻辑成功,返回响应数据,作为axios成功的结果
    return res.data
  },
  (err) => {
    if (err.response.status === 401) {
      // 1. 删除用户信息
      const store = useUserStore()
      store.delUser()
      // 2. 跳转登录,带上接口失效所在页面的地址,登录完成后回跳使用
      router.push({
        path: '/login',
        query: { returnUrl: router.currentRoute.value.fullPath }
      })
    }
    return Promise.reject(err)
  }
)

封装请求函数

封装一个统一的请求函数,简化请求配置

image-20250303180205070

测试

image-20250303180345053

设置响应数据类型

image-20250303181056971

问题:为什么T传递给了instance.request<any, T>的第二个参数。

解释:第一个参数是给res设置的类型,但是在响应拦截器中返回的是res.data的数据,如果想给它设置类型,只能通过第二个参数。

打包svg地图@

Login

路由规则

1、在router/index.ts中配置路由匹配规则

image-20250310105922638

2、在App.vue中配置一级路由占位

image-20250310105747226

组件:cp-nav-bar

image-20250310113417886

页面布局

1、vant相关属性

image-20250310113504623

2、在login/index.vue中使用组件

image-20250310113649671

3、实现 <cp-nav-bar> 组件

image-20250310113954639

image-20250310113957700

4、修改样式

注意:使用 :deep() 修改vant组件内部样式。

image-20250310114052249

image-20250310114320695

image-20250310114334561

功能

动态标题、右侧文字

思路:通过props来动态设置标题、右侧文字。

1、在子组件中,通过defineProps()接收传递的属性 title、rightText。

image-20250310114846440

2、在父组件中,使用<cp-nav-bar> 组件时绑定属性 title、rightText。

image-20250310115108968

右侧文字点击事件

思路:通过emit方法触发自定义右侧文字点击事件。

1、在子组件中,监听vant组件的@click-right事件,同时自定义@click-right事件向外发射该事件。

image-20250310115637890

2、在父组件中,监听子组件传递过来的自定义事件@click-right

image-20250310115902422

返回功能

image-20250310120108891

思路:通过 history.state访问历史记录信息。

image-20250310120258053

1、在子组件中,监听@click-left事件

image-20250310120904360

2、判断history.state.back是否有值,实现回退到不同页面。

image-20250310121040977

组件类型提示@

问题:当前的组件<cp-nav-bar>是没有类型的:

image-20250310121414204

解决:

  • 思路一:在使用时,显示(手动)导入组件。

    image-20250310121523402

  • 思路二:对于全局组件或自动注册的组件,可以在components.d.ts文件中添加全局组件类型。

    image-20250310122052172

组件:login

image-20250310122912994

页面布局

1、在style/main.scss中全局重置样式

less
// 全局样式
body {
  font-size: 14px;
  color: var(--cp-text1);
}
a {
  color: var(--cp-text2);
}
h1,h2,h3,h4,h5,h6,p,ul,ol {
  margin: 0;
  padding: 0;
}

2、页面结构

vue
<script setup lang="ts"></script>

<template>
  <div class="login-page">
    <!-- 导航栏 -->
    <cp-nav-bar
      right-text="注册"
      @click-right="$router.push('/register')"
    ></cp-nav-bar>
    <!-- 头部 -->
    <div class="login-head">
      <h3>密码登录</h3>
      <a href="javascript:;">
        <span>短信验证码登录</span>
        <van-icon name="arrow"></van-icon>
      </a>
    </div>
    <!-- 表单 -->
    <van-form autocomplete="off">
      <van-field placeholder="请输入手机号" type="tel"></van-field>
      <van-field placeholder="请输入密码" type="password"></van-field>
      <div class="cp-cell">
        <van-checkbox>
          <span>我已同意</span>
          <a href="javascript:;">用户协议</a>
          <span>及</span>
          <a href="javascript:;">隐私条款</a>
        </van-checkbox>
      </div>
      <div class="cp-cell">
        <van-button block round type="primary">登 录</van-button>
      </div>
      <div class="cp-cell">
        <a href="javascript:;">忘记密码?</a>
      </div>
    </van-form>
    <!-- 底部 -->
    <div class="login-other">
      <van-divider>第三方登录</van-divider>
      <div class="icon">
        <img src="@/assets/qq.svg" alt="" />
      </div>
    </div>
  </div>
</template>

3、样式

less

.login {
  &-page {
    padding-top: 46px;
  }
  &-head {
    display: flex;
    padding: 30px 30px 50px;
    justify-content: space-between;
    align-items: flex-end;
    line-height: 1;
    h3 {
      font-weight: normal;
      font-size: 24px;
    }
    a {
      font-size: 15px;
    }
  }
  &-other {
    margin-top: 60px;
    padding: 0 30px;
    .icon {
      display: flex;
      justify-content: center;
      img {
        width: 36px;
        height: 36px;
        padding: 4px;
      }
    }
  }
}
.van-form {
  padding: 0 14px;
  .cp-cell {
    height: 52px;
    line-height: 24px;
    padding: 14px 16px;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    .van-checkbox {
      a {
        color: var(--cp-primary);
        padding: 0 5px;
      }
    }
  }
  .btn-send {
    color: var(--cp-primary);
    &.active {
      color: rgba(22,194,163,0.5);
    }
  }
}

4、在style/main.scss中修改样式

less
  // 覆盖vant主体色
  --van-primary-color: var(--cp-primary);
  // 单元格上下间距
  --van-cell-vertical-padding: 14px;
  // 复选框大小
  --van-checkbox-size: 14px;
  // 默认按钮文字大小
  --van-button-normal-font-size: 16px;

5、清除多余app容器

image-20250310123355365

image-20250310123413871

功能

表单校验
提取校验规则@

1、提取校验规则到utils/rule.ts文件。

image-20250310124132700

2、设置校验规则的TS类型

image-20250310124611444

image-20250310124845618

基本校验

1、校验手机号

image-20250310124302262

2、校验密码

image-20250310125121492

3、表单整体校验,修改native-type属性

image-20250310125441939

校验勾选协议

1、绑定 agree ,判断是否勾选协议

image-20250310125756052

2、在submit事件处理方法中,校验是否勾选协议

image-20250310130006839

密码登录

image-20250310130155236

接口
  • URL/login/password

  • 类型POST

  • 参数

    ts
    {
      password: string, // 密码
      mobile: string // 手机号
    }
  • 返回数据

    image-20250310130935681

密码登录

1、在 service/user.ts 中发送网络请求

image-20250310131253423

2、在 login/login.vue 中执行密码登录

image-20250310131631424

短信登录

image-20250310131810542

切换界面

image-20250310132012754

1、根据 isPass 切换密码登录和短信验证码登录界面

image-20250310132326574

2、表单项切换

image-20250310132724441

3、校验验证码

image-20250310132853290

image-20250310132924867

获取验证码-接口

URL/code

类型GET

token:携带

参数

ts
{
  mobile: string, // 手机号
  type: 'login' | 'register' | 'changeMobile' | 'forgetPassword' | 'bindMobile'
}

返回数据

image-20250310152414622

获取验证码

image-20250310151403819

1、发送前校验

image-20250310152643229

2、在 service/user.ts 中发送网络请求

image-20250310153132644

3、在types/user.ts中定义CodeType联合类型

image-20250310153209924

4、在login组件中,发送请求获取验证码,并设置倒计时

image-20250310153318554

5、实现倒计时,并在结束时清理定时器

image-20250310153455243

6、在组件销毁时清理定时器

image-20250310153546991

7、显示倒计时

image-20250310153843511

image-20250310153733064

短信登录-接口

URL/login

类型POST

token:携带

参数

ts
{
  code: string, // 验证码
  mobile: string // 手机号
}

返回数据

image-20250310151325514

短信登录

image-20250310154258148

1、在 service/user.ts 中发送网络请求

image-20250310154429693

2、在组件中将短信登录合并到密码登录逻辑中

image-20250310154659093

密码是否可见

image-20250310160421960

image-20250310161018568

Layout

image-20250310164756675

路由规则

1、在router/index.ts中配置路由匹配规则

image-20250310165359906

2、在views/layout/layout.vue中配置二级路由占位

image-20250310165202700

组件:van-tabbar

image-20250310170742288

image-20250310165538026

1、基本使用

image-20250310165756546

2、开启路由模式

image-20250310165914436

3、自定义图标

image-20250310170615944

4、修改样式

less
.layout-page {
  :deep() {
    .van-tabbar-item {
      &__icon {
        font-size: 21px;
      }
      &__text {
        font-size: 11px;
      }
      &:not(.van-tabbar-item--active) {
        color: var(--cp-text3);
      }
    }
  }
}

功能

访问权限控制

router.beforeEach()(guard)全局前置导航守卫,用于在每次路由跳转前执行自定义逻辑,如权限校验、数据预加载等。


image-20250310171247951

思路:在 router/index.ts 中添加全局前置导航守卫,校验是否有token、是否在白名单中。

白名单:不需要登录就可以访问的页面

image-20250310171558760

页面标题

router.afterEach()(guard)全局后置守卫,用于注册一个在 导航完成之后 执行的钩子函数。它不会改变导航结果,常用于执行与导航结果无关的后续处理操作,如埋点统计、页面标题更新等。


1、在路由配置元信息 meta 中定义标题

image-20250310172259781

2、在 router/index.ts 中添加全局后置导航守卫,获取 meta 并设置标题

3、TS:如果需要TS提示title,可以通过扩展元信息类型实现

image-20250310172539251

解决:新建 types/vue-router.d.ts 文件,并扩展元信息类型

image-20250310172745327

加载进度

依赖包nprogress

1、安装 nprogress@types/nprogress

sh
pnpm i nprogress
pnpm i @types/nprogress -D # TS类型提示

2、在 router/index.ts 中导入并在前置守卫中开启

image-20250310175032571

3、在 router/index.ts 中的后置守卫中结束

image-20250310175045125

4、取消进度条的小圆圈动画

image-20250310175236727

image-20250310175316275

5、在 style/main.less 中修改进度条样式

image-20250310175432008

User

组件:user-info

image-20250310180734357

image-20250310180757275

TS类型

知识点Omit

知识点Pick

ts
// 用户信息
export type User = {
  token: string
  id: string
  account: string
  mobile: string
  avatar: string
}
ts
// 个人信息
type OmitUser = Omit<User, 'token'>
export type UserInfo = OmitUser & {
  /** 关注 */
  likeNumber: number
  /** 收藏 */
  collectionNumber: number
  /** 积分 */
  score: number
  /** 优惠券 */
  couponNumber: number
  orderInfo: {
    /** 待付款 */
    paidNumber: number 
    /** 待发货 */
    receivedNumber: number
    /** 待收货 */
    shippedNumber: number
    /** 已完成 */
    finishedNumber: number
  }
}

页面布局

html
<script setup lang="ts"></script>

<template>
  <div class="user-page">
    <div class="user-page-head">
      <!-- 头部 -->
      <div class="top">
        <van-image
          round
          fit="cover"
          src="https://yanxuan-item.nosdn.127.net/ef302fbf967ea8f439209bd747738aba.png"
        />
        <div class="name">
          <p>用户907456</p>
          <p><van-icon name="edit" /></p>
        </div>
      </div>
      <!-- 用户信息 -->
      <van-row>
        <van-col span="6">
          <p>150</p>
          <p>收藏</p>
        </van-col>
        <van-col span="6">
          <p>23</p>
          <p>关注</p>
        </van-col>
        <van-col span="6">
          <p>270</p>
          <p>积分</p>
        </van-col>
        <van-col span="6">
          <p>3</p>
          <p>优惠券</p>
        </van-col>
      </van-row>
    </div>
    <!-- 药品订单 -->
    <div class="user-page-order">
      <div class="head">
        <h3>药品订单</h3>
        <router-link to="/order">全部订单 <van-icon name="arrow" /></router-link>
      </div>
      <van-row>
        <van-col span="6">
          <cp-icon name="user-paid" />
          <p>待付款</p>
        </van-col>
        <van-col span="6">
          <cp-icon name="user-shipped" />
          <p>待发货</p>
        </van-col>
        <van-col span="6">
          <cp-icon name="user-received" />
          <p>待收货</p>
        </van-col>
        <van-col span="6">
          <cp-icon name="user-finished" />
          <p>已完成</p>
        </van-col>
      </van-row>
    </div>
  </div>
</template>

2、样式

less

.user-page {
  background-color: var(--cp-bg);
  min-height: calc(100vh - 50px);
  padding: 0 15px 65px;
  // 头部
  &-head {
    height: 200px;
    background: linear-gradient(180deg, rgba(44, 181, 165, 0.46), rgba(44, 181, 165, 0));
    margin: 0 -15px;
    padding: 0 15px;
    .top {
      display: flex;
      padding-top: 50px;
      align-items: center;
      .van-image {
        width: 70px;
        height: 70px;
      }
      .name {
        padding-left: 10px;
        p {
          &:first-child {
            font-size: 18px;
            font-weight: 500;
          }
          &:last-child {
            margin-top: 10px;
            color: var(--cp-primary);
            font-size: 16px;
          }
        }
      }
    }
    .van-row {
      margin: 0 -15px;
      padding-top: 15px;
      p {
        text-align: center;
        &:first-child {
          font-size: 18px;
          font-weight: 500;
        }
        &:last-child {
          color: var(--cp-dark);
          font-size: 12px;
          padding-top: 4px;
        }
      }
    }
  }
  // 订单
  &-order {
    background-color: #fff;
    border-radius: 8px;
    margin-bottom: 15px;
    padding-bottom: 15px;
    .head {
      display: flex;
      justify-content: space-between;
      line-height: 50px;
      padding: 0 15px;
      a {
        color: var(--cp-tip);
      }
    }
    .van-col {
      text-align: center;
      .cp-icon {
        font-size: 28px;
      }
      p {
        font-size: 12px;
        padding-top: 4px;
      }
    }
  }
  // 分组
  &-group {
    background-color: #fff;
    border-radius: 8px;
    overflow: hidden;
    h3 {
      padding-left: 16px;
      line-height: 44px;
    }
    .van-cell {
      align-items: center;
    }
    .cp-icon {
      font-size: 17px;
      margin-right: 10px;
    }
  }
  .logout {
    display: block;
    margin: 20px auto;
    width: 100px;
    text-align: center;
    color: var(--cp-price);
  }
}

接口

  • URL/patient/myUser

  • 类型GET

  • token:携带

  • 参数:无

  • 返回数据

    image-20250310175952745

渲染页面

1、在 service/user.ts 中发送网络请求

image-20250311151835774

2、在组件中调用网路请求方法

image-20250311152309091

2、在组件中渲染请求的数据

image-20250311152315816

image-20250311152624233

组件:user-quick-toolbar

image-20250311152758029

页面布局

1、快捷工具数据

image-20250311153714520

2、遍历渲染

image-20250311153417722

3、退出登录

image-20250311153532785

功能

退出登录

1、绑定点击事件

image-20250311153633747

2、实现点击事件处理函数

image-20250311154052780

Patient

image-20250311154211981

路由规则

image-20250311154517741

组件:patient-list

image-20250311155040181

image-20250311154305987

页面布局

1、HTML

html
<script setup lang="ts"></script>

<template>
  <div class="patient-page">
    <!-- 导航栏 -->
    <cp-nav-bar title="家庭档案"></cp-nav-bar>
      
    <!-- 患者列表 -->
    <div class="patient-list">
      <div class="patient-item">
        <div class="info">
          <span class="name">李富贵</span>
          <span class="id">321111********6164</span>
          <span>男</span>
          <span>32岁</span>
        </div>
        <div class="icon"><cp-icon name="user-edit" /></div>
        <div class="tag">默认</div>
      </div>
      <div class="patient-item">
        <div class="info">
          <span class="name">李富贵</span>
          <span class="id">321333********6164</span>
          <span>男</span>
          <span>32岁</span>
        </div>
        <div class="icon"><cp-icon name="user-edit" /></div>
      </div>
      
      <!-- 添加患者 -->
      <div class="patient-add">
        <cp-icon name="user-add" />
        <p>添加患者</p>
      </div>
      
      <!-- 提示 -->
      <div class="patient-tip">最多可添加 6 人</div>
    </div>
  </div>
</template>

2、样式

less
.patient-page {
  padding: 46px 0 80px;
}
.patient-list {
  padding: 15px;
}
.patient-item {
  display: flex;
  align-items: center;
  padding: 15px;
  background-color: var(--cp-bg);
  border-radius: 8px;
  margin-bottom: 15px;
  position: relative;
  border: 1px solid var(--cp-bg);
  transition: all 0.3s;
  overflow: hidden;
  .info {
    display: flex;
    flex-wrap: wrap;
    flex: 1;
    span {
      color: var(--cp-tip);
      margin-right: 20px;
      line-height: 30px;
      &.name {
        font-size: 16px;
        color: var(--cp-text1);
        width: 80px;
        margin-right: 0;
      }
      &.id {
        color: var(--cp-text2);
        width: 180px;
      }
    }
  }
  .icon {
    color: var(--cp-tag);
    width: 20px;
    text-align: center;
  }
  .tag {
    position: absolute;
    right: 60px;
    top: 21px;
    width: 30px;
    height: 16px;
    font-size: 10px;
    color: #fff;
    background-color: var(--cp-primary);
    border-radius: 2px;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  &.selected {
    border-color: var(--cp-primary);
    background-color: var(--cp-plain);
    .icon {
      color: var(--cp-primary);
    }
  }
}
.patient-add {
  background-color: var(--cp-bg);
  color: var(--cp-primary);
  text-align: center;
  padding: 15px 0;
  border-radius: 8px;
  .cp-icon {
    font-size: 24px;
  }
}
.patient-tip {
  color: var(--cp-tag);
  padding: 12px 0;
}
.pb4 {
  padding-bottom: 4px;
}

接口

  • URL/patient/mylist

  • 类型GET

  • token:携带

  • 参数:无

  • 返回数据

    image-20250311155326154

TS类型

ts
// 家庭档案-患者信息
export type Patient = {
  /** 患者ID */
  id: string
  /** 患者名称 */
  name: string
  /** 身份证号 */
  idCard: string
  /** 0不默认  1默认 */
  defaultFlag: 0 | 1
  /** 0 女  1 男 */
  gender: 0 | 1
  /** 性别文字 */
  genderValue: string
  /** 年龄 */
  age: number
}

// 家庭档案-患者信息列表
export type PatientList = Patient[]

渲染页面

1、在 services/user.ts 中发送网络请求

image-20250311155631577

2、在组件中,调用请求方法,获取数据

image-20250311155918572

3、渲染数据

image-20250311160216595

功能

身份证脱敏@

知识点:通过$1$2可以获取正则匹配到的内容。

image-20250311160430742

添加患者

image-20250311160805249

image-20250311160853145

组件:cp-radio-btn

image-20250311161102807

image-20250311161157371

页面布局

1、HTML

html
<script setup lang="ts"></script>

<template>
  <div class="cp-radio-btn">
    <a class="item" href="javascript:;">男</a>
    <a class="item" href="javascript:;">女</a>
  </div>
</template>

2、样式

less
.cp-radio-btn {
  display: flex;
  flex-wrap: wrap;
  .item {
    height: 32px;
    min-width: 60px;
    line-height: 30px;
    padding: 0 14px;
    text-align: center;
    border: 1px solid var(--cp-bg);
    background-color: var(--cp-bg);
    margin-right: 10px;
    box-sizing: border-box;
    color: var(--cp-text2);
    margin-bottom: 10px;
    border-radius: 4px;
    transition: all 0.3s;
    &.active {
      border-color: var(--cp-primary);
      background-color: var(--cp-plain);
    }
  }
}
动态渲染选项

1、定义选项数据

image-20250311161436973

2、使用组件,并传入数据

image-20250311161529504

3、接收并遍历渲染数据

image-20250311161727446

image-20250311161810230

功能
切换选中项

1、在父组件中定义 gender 属性,并绑定到 modelValue上

image-20250311162129024

2、在子组件中接收 gender 属性,并根据gender值设置active样式

image-20250311165422439

3、绑定点击事件,向外发射自定义事件 @update:modelValue

image-20250311162549689

4、在父组件中绑定子组件传递的自定义事件 @update:modelValue

image-20250311162721418

5、重构:使用v-model重构

image-20250311162811455

显示弹层

1、在 patient-list组件中,使用 van-popup 组件添加弹层

image-20250311163904526

image-20250311164124121

2、点击 添加患者 按钮,展示弹层

image-20250311163858961

image-20250311163839148

3、修改弹层样式

less
.patient-page {
  padding: 46px 0 80px;
  :deep() {
    .van-popup {
      width: 80%;
      height: 100%;
    }
  }
}

组件:patient-add

image-20250311160805249

image-20250311165610015

image-20250311165905597

页面布局
html
      <van-form autocomplete="off" ref="form">
        <van-field label="真实姓名" placeholder="请输入真实姓名" />
        <van-field label="身份证号" placeholder="请输入身份证号" />
        <van-field label="性别" class="pb4">
          <!-- 单选按钮组件 -->
          <template #input>
            <cp-radio-btn :options="options"></cp-radio-btn>
          </template>
        </van-field>
        <van-field label="默认就诊人">
          <template #input>
            <van-checkbox :icon-size="18" round />
          </template>
        </van-field>
      </van-form>
接口-添加患者
  • URL/patient/add

  • 类型POST

  • token:携带

  • 参数

    ts
    {
      name: string // 患者姓名
      idCard: string // 患者身份证号
      defaultFlag: number // 是否设置为默认患者
      gender: number // 性别,1:男,0:女
    }
  • 返回数据

    image-20250311174054995

TS类型修改

问题:patient表单只需要4个属性,而Patient类型有7个属性,因此需要将其他属性修改为可选属性。

image-20250311170200533

解决:修改Patient类型为可选属性。

image-20250311170243725

渲染表单

image-20250311170742667

功能
重构cp-nav-bar

1、在 cp-nav-bar 组件中定义 back 属性,类型是一个回调函数

image-20250311164657982

2、重构 onClickLeft 方法,如果传入了back,则执行该回调,而不是之前的逻辑

image-20250311164823882

3、在父组件中,传入back属性,实现自定义的关闭弹层逻辑

image-20250311164931925

默认就诊人类型转换

问题:默认就诊人给的数据是 0 和 1,而 van-checkbox 值的类型为 true 和 false,需要通过计算属性转换后才能使用。

解决:通过计算属性转换后使用。

image-20250311171203009

image-20250311171206654

重置表单

需求:每次打开侧滑弹层时,需要将上次弹层中的表单数据清空。

image-20250311171605583

表单校验

image-20250311171802427

image-20250311171824086

1、表单项校验

image-20250311172229098

image-20250311172454192

image-20250311172539724

2、提交时校验整个表单

image-20250311173050584

image-20250311173106083

3、性别确认提示

思路:身份证号倒数第二位,如果是偶数就是女,如果是奇数就是男。

image-20250311173701339

实现添加患者

image-20250311173745473

1、在 service/user.ts 中发送网络请求

image-20250311174216419

2、在组件中调用请求方法,实现添加患者

image-20250311174430313

3、注意:如果添加的身份证号已经存在于患者列表中会提示添加失败

image-20250311174600870

编辑患者

image-20250311174830111

思路:编辑患者和添加患者共用一个组件。

image-20250311174855173

接口

  • URL/patient/update

  • 类型PUT

  • token:携带

  • 参数

    ts
    {
      name: string // 患者姓名
      idCard: string // 患者身份证号
      defaultFlag: number // 是否设置为默认患者
      gender: number // 性别,1:男,0:女
      id: string // 患者信息id
    }
  • 返回数据

    image-20250311175128135

功能

显示弹层

image-20250311180655453

image-20250311175737350

区分标题

思路:根据 patient 对象是否存在id属性,判断是否是编辑状态,显示不同的标题

image-20250311175924818

实现编辑患者

1、在 service/user.ts 中发送网络请求

image-20250311180120697

2、在组件中调用请求方法,将编辑患者逻辑合并到添加患者中

image-20250311180404698

清空校验

需求:当再次打开弹层时,上次的校验结果依然存在,需要清空。

image-20250311180743811

删除患者

image-20250311181117259

接口

  • URL/patient/del/{id}

  • 类型DELETE

  • token:携带

  • 参数

    ts
    id: string // 患者信息id
  • 返回数据

    image-20250311180929457

功能

删除按钮

image-20250311181341484

需求:在弹层的底部添加一个删除按钮

image-20250311181448704

修改样式

less
// 底部操作栏
.van-action-bar {
  padding: 0 10px;
  margin-bottom: 10px;
  .van-button {
    color: var(--cp-price);
    background-color: var(--cp-bg);
  }
}
实现删除患者

1、在 service/user.ts 中发送网络请求

image-20250312143633869

2、在组件中调用请求方法,实现删除患者

image-20250312144110768

image-20250312144029201

Home

image-20250312144227465

页面布局

image-20250312144251967

1、HTML

html
<script setup lang="ts"></script>

<template>
  <div class="home-page">
    <!-- 头部 -->
    <div class="home-header">
      <div class="con">
        <h1>优医</h1>
        <div class="search">
          <cp-icon name="home-search" /> 搜一搜:疾病/症状/医生/健康知识
        </div>
      </div>
    </div>
    <!-- 导航 -->
    <div class="home-navs">
      <van-row>
        <van-col span="8">
          <router-link to="/" class="nav">
            <cp-icon name="home-doctor"></cp-icon>
            <p class="title">问医生</p>
            <p class="desc">按科室查问医生</p>
          </router-link>
        </van-col>
        <van-col span="8">
          <router-link to="/consult/fast" class="nav">
            <cp-icon name="home-graphic"></cp-icon>
            <p class="title">极速问诊</p>
            <p class="desc">20s医生极速回复</p>
          </router-link>
        </van-col>
        <van-col span="8">
          <router-link to="/" class="nav">
            <cp-icon name="home-prescribe"></cp-icon>
            <p class="title">开药门诊</p>
            <p class="desc">线上买药更方便</p>
          </router-link>
        </van-col>
      </van-row>
      <van-row>
        <van-col span="6">
          <router-link to="/" class="nav min">
            <cp-icon name="home-order"></cp-icon>
            <p class="title">药品订单</p>
          </router-link>
        </van-col>
        <van-col span="6">
          <router-link to="/" class="nav min">
            <cp-icon name="home-docs"></cp-icon>
            <p class="title">健康档案</p>
          </router-link>
        </van-col>
        <van-col span="6">
          <router-link to="/" class="nav min">
            <cp-icon name="home-rp"></cp-icon>
            <p class="title">我的处方</p>
          </router-link>
        </van-col>
        <van-col span="6">
          <router-link to="/" class="nav min">
            <cp-icon name="home-find"></cp-icon>
            <p class="title">疾病查询</p>
          </router-link>
        </van-col>
      </van-row>
    </div>
    <!-- 轮播图 -->
    <div class="home-banner">
      <van-swipe indicator-color="#fff">
        <van-swipe-item>
          <img src="@/assets/ad.png" alt="" />
        </van-swipe-item>
        <van-swipe-item>
          <img src="@/assets/ad.png" alt="" />
        </van-swipe-item>
      </van-swipe>
    </div>
    <!-- 知识列表tab -->
    <van-tabs shrink sticky v-model:active="active">
      <van-tab title="关注">1</van-tab>
      <van-tab title="推荐" >
        <p v-for="i in 100" :key="i">内容</p>
      </van-tab>
      <van-tab title="减脂">3</van-tab>
      <van-tab title="饮食">4</van-tab>
    </van-tabs>
  </div>
</template>

2、样式

less
.home-page {
  padding-bottom: 50px;
}
.home-header {
  height: 100px;
  position: relative;
  &::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 90px;
    background: linear-gradient(180deg, rgba(62, 206, 197, 0.85), #26bcc6);
    border-bottom-left-radius: 150px 20px;
    border-bottom-right-radius: 150px 20px;
  }
  .con {
    position: relative;
    padding: 0 15px;
    > h1 {
      font-size: 18px;
      color: #fff;
      font-weight: normal;
      padding: 20px 0;
      line-height: 1;
      padding-left: 5px;
    }
    .search {
      height: 40px;
      border-radius: 20px;
      box-shadow: 0px 15px 22px -7px rgba(224, 236, 250, 0.8);
      background-color: #fff;
      display: flex;
      align-items: center;
      padding: 0 20px;
      color: var(--cp-dark);
      font-size: 13px;
      .cp-icon {
        font-size: 16px;
        margin-right: 5px;
      }
    }
  }
}
.home-navs {
  padding: 10px 15px 0 15px;
  .nav {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 10px 0;
    .cp-icon {
      font-size: 48px;
    }
    .title {
      font-weight: 500;
      margin-top: 5px;
      color: var(--cp-text1);
    }
    .desc {
      font-size: 11px;
      color: var(--cp-tag);
      margin-top: 2px;
    }
    &.min {
      .cp-icon {
        font-size: 31px;
      }
      .title {
        font-size: 13px;
        color: var(--cp-text2);
        font-weight: normal;
      }
    }
  }
}
.home-banner {
  padding: 10px 15px;
  height: 100px;
  img {
    width: 100%;
    height: 100%;
  }
}

3、全局覆盖van-tab样式

less
// 全局覆盖van-tab样式
.van-tabs {
  .van-tabs__nav {
    padding: 0 0 15px 0;
  }
  .van-tabs__line {
    width: 20px;
    background-color: var(--cp-primary);
  }
  .van-tab {
    padding: 0 15px;
  }
}

组件:knowledge-card

页面布局

1、HTML

html
<template>
    <div class="knowledge-card van-hairline--bottom">
      <div class="head">
        <van-image
          round
          class="avatar"
          src="https://yanxuan-item.nosdn.127.net/9ad83e8d9670b10a19b30596327cfd14.png"
        ></van-image>
        <div class="info">
          <p class="name">张医生</p>
          <p class="dep van-ellipsis">积水潭医院 骨科 主任医师</p>
        </div>
        <van-button class="btn" size="small" round>+ 关注</van-button>
      </div>
      <div class="body">
        <h3 class="title van-ellipsis">高血压是目前世界上最常见,发病率最高的慢性病之一</h3>
        <p class="tag">
          <span># 肥胖</span>
          <span># 养生</span>
        </p>
        <p class="intro van-multi-ellipsis--l2">
          据估计,全世界有 10
          亿人患有高血压,来自美国全国健康和营养调查的数据(NHANES)显示,高血压的患病率呈逐年上升趋势。
          但是,我国高血压的控制程度非常不乐观,不少朋友担心降压药对肾的影响,有些甚至因为担心伤肾,而不敢吃降压药。
          我们就介绍一下,高血压对肾脏的危害,还有降压药对肾脏影响。
          没有耐心看的朋友,可以直接记住这个结论:高血压比降压药伤肾。千万不要因为担心副作用不敢吃药,那是「丢西瓜捡芝麻」得不偿失的行为
        </p>
        <div class="imgs">
          <van-image
            src="https://yanxuan-item.nosdn.127.net/c1cdf62c5908659a9e4c8c2f9df218fd.png"
          />
          <van-image
            src="https://yanxuan-item.nosdn.127.net/c1cdf62c5908659a9e4c8c2f9df218fd.png"
          />
          <van-image
            src="https://yanxuan-item.nosdn.127.net/c1cdf62c5908659a9e4c8c2f9df218fd.png"
          />
        </div>
        <p class="logs">
          <span>10 收藏</span>
          <span>50 评论</span>
        </p>
      </div>
    </div>
</template>

2、样式

less
.knowledge-card {
  padding: 20px 0 16px;
  .head {
    display: flex;
    align-items: center;
    .avatar {
      width: 38px;
      height: 38px;
      margin-right: 10px;
    }
    .info {
      width: 200px;
      padding-right: 10px;
      .name {
        color: var(--cp-text2);
      }
      .dep {
        color: var(--cp-tip);
        font-size: 12px;
      }
    }
    .btn {
      padding: 0 12px;
      border-color: var(--cp-primary);
      color: var(--cp-primary);
      height: 28px;
      width: 72px;
    }
  }
  .body {
    .title {
      font-size: 16px;
      margin-top: 8px;
      font-weight: normal;
    }
    .tag {
      margin-top: 6px;
      > span {
        color: var(--cp-primary);
        margin-right: 20px;
        font-size: 12px;
      }
    }
    .intro {
      margin-top: 7px;
      line-height: 2;
      color: var(--cp-text3);
    }
    .imgs {
      margin-top: 7px;
      display: flex;
      .van-image {
        width: 106px;
        height: 106px;
        margin-right: 12px;
        border-radius: 12px;
        overflow: hidden;
        &:last-child {
          margin-right: 0;
        }
      }
      &.large {
        .van-image {
          width: 185px;
          height: 125px;
        }
      }
    }
    .logs {
      margin-top: 10px;
      > span {
        color: var(--cp-tip);
        margin-right: 16px;
        font-size: 12px;
      }
    }
  }
}

组件:knowledge-list

页面布局

1、HTML

html
<template>
  <div class="knowledge-list">
    <knowledge-card v-for="i in 5" :key="i"></knowledge-card>
  </div>
</template>

2、样式

less
.knowledge-list {
  padding: 0 15px;
}

3、在Home组件中使用 knowlege-list

html
    <van-tabs shrink sticky v-model:active="active">
      <van-tab title="关注"><knowledge-list /></van-tab>
      <van-tab title="推荐"><knowledge-list /></van-tab>
      <van-tab title="减脂"><knowledge-list /></van-tab>
      <van-tab title="饮食"><knowledge-list /></van-tab>
    </van-tabs>

功能

列表加载更多

image-20250312145402168

1、使用 van-list 实现列表加载更多功能

image-20250312150948185

2、模拟加载后台数据

image-20250312151155470

3、遍历加载的数据

image-20250312151234299

渲染请求数据

image-20250312151515966

TS类型

1、响应数据类型

ts
// 文章信息类型
export type Knowledge = {
  id: string
  /** 标题 */
  title: string
  /** 封面[] */
  coverUrl: string[]
  /** 标签[] */
  topics: string[]
  /** 收藏数 */
  collectionNumber: number
  /** 评论数 */
  commentNumber: number
  /** 医生名称 */
  creatorName: string
  /** 医生头像 */
  creatorAvatar: string
  /** 医生医院 */
  creatorHospatalName: string
  /** 关注文章 */
  likeFlag: 0 | 1
  /** 内容 */
  content: string
  /** 医生科室 */
  creatorDep: string
  /** 医生职称 */
  creatorTitles: string
  /** 医生ID */
  creatorId: string
}

// 文章列表
export type KnowledgeList = Knowledge[]

// 文章列表带分页
export type KnowledgePage = {
  pageTotal: number
  total: number
  rows: KnowledgeList
}

2、查询参数类型

ts
// props类型 recommend推荐,fatReduction减脂,food健康饮食,like关注医生页面文章
export type KnowledgeType = 'like' | 'recommend' | 'fatReduction' | 'food'

// 文章列表查询参数
export type KnowledgeParams = {
  type: KnowledgeType
  current: number
  pageSize: number
}
知识列表类型

1、在 kownlege-list 组件中,定义props类型

image-20250312152416900

2、在使用组件时添加type属性

image-20250312152345578

接口
  • URL/patient/home/knowledge

  • 类型GET

  • token:携带

  • 参数

    ts
    {
      type: KnowledgeType // recommend: 推荐,fatReduction: 减脂,food: 健康饮食,like: 关注医生页面文章
      current: number
      pageSize: number
    }
  • 返回数据

    image-20250312155506866

渲染数据

image-20250312152525103

1、在 services/home.ts 中发送网络请求

image-20250312152821133

2、在组件中调用请求方法,获取知识列表数据

image-20250312153344082

3、遍历并传递数据到 kownlege-card 中

image-20250312153515825

4、渲染数据到 kownlege-card 中

image-20250312154243356

image-20250312154233107

5、修复:图片变形

image-20250312154333436

image-20250312154358327

6、修复:去除HTML代码

image-20250312154431204

image-20250312154551724

7、修复:一张配图展示

image-20250312154741345

组件:doctor-card

页面布局

1、HTML

html
<template>
  <div class="doctor-card">
    <van-image
      round
      src="https://yanxuan-item.nosdn.127.net/3cb61b3fd4761555e56c4a5f19d1b4b1.png"
    />
    <p class="name">周医生</p>
    <p class="van-ellipsis">积水潭医院 神经内科</p>
    <p>副主任医师</p>
    <van-button round size="small" type="primary">+ 关注</van-button>
  </div>
</template>

2、样式

less
.doctor-card {
  width: 135px;
  height: 190px;
  background: #fff;
  border-radius: 20px;
  box-shadow: 0px 0px 11px 0px rgba(229, 229, 229, 0.2);
  text-align: center;
  padding: 15px;
  margin-left: 15px;
  display: inline-block;
  box-sizing: border-box;
  > .van-image {
    width: 58px;
    height: 58px;
    vertical-align: top;
    border-radius: 50%;
    margin: 0 auto 8px;
  }
  > p {
    margin-bottom: 0;
    font-size: 11px;
    color: var(--cp-tip);
    &.name {
      font-size: 13px;
      color: var(--cp-text1);
      margin-bottom: 5px;
    }
  }
  > .van-button {
    padding: 0 12px;
    height: 28px;
    margin-top: 8px;
    width: 72px;
  }
}

组件:follow-doctor

image-20250312155747347

image-20250312155753496

页面布局

1、使用组件

image-20250312160620521

1、HTML

html
<template>
  <div class="follow-doctor">
    <div className="head">
      <p>推荐关注</p>
      <a href="javascript:;"> 查看更多<i class="van-icon van-icon-arrow" /></a>
    </div>
    <div class="body">
      <!-- swipe 组件 -->
    </div>
  </div>
</template>

2、使用 van-swipe 组件遍历包裹 doctor-card 组件

image-20250312161006898

3、样式

less
.follow-doctor {
  background-color: var(--cp-bg);
  height: 250px;
  .head {
    display: flex;
    justify-content: space-between;
    height: 45px;
    align-items: center;
    padding: 0 15px;
    font-size: 13px;
    > a {
      color: var(--cp-tip);
    }
  }
  .body {
    width: 100%;
    overflow: hidden;
  }
}

渲染请求数据

image-20250312170826776

TS类型
ts
// 医生卡片对象
export type Doctor = {
  /** 医生ID */
  id: string
  /** 医生名称 */
  name: string
  /** 头像 */
  avatar: string
  /** 医院名称 */
  hospitalName: string
  /** 医院等级 */
  gradeName: string
  /** 科室 */
  depName: string
  /** 职称 */
  positionalTitles: string
  /** 是否关注,0 未关注 1 已关注 */
  likeFlag: 0 | 1
  /** 接诊服务费 */
  serviceFee: number
  /** 接诊人数 */
  consultationNum: number
  /** 评分 */
  score: number
  /** 主攻方向 */
  major: string
}
接口
  • URL/home/page/doc

  • 类型GET

  • token:携带

  • 参数

    ts
    {
      current: number
      pageSize: number
    }
  • 返回数据

    image-20250312160418033

渲染数据

1、在 services/home.ts 中发送网络请求

image-20250312171243891

2、在组件中调用请求方法,获取医生列表数据

image-20250312171633271

3、遍历并传递数据到 doctor-card 中

image-20250312171619907

4、渲染数据到 doctor-card 中

image-20250312171733742

image-20250312171840407

功能

调整卡片间距

需求:需要调整 van-swipe-item 宽度,让卡片更加紧凑

image-20250312161328937

实现

image-20250312161410372

清除无限滚动

image-20250312161521021

清除指示器

image-20250312161500614

适配滑动宽度@

image-20250312162004908

image-20250312162346335

依赖包vueuse

安装

sh
pnpm i @vueuse/core

宽度公式375 / 150 = 设备宽度 / x,设备宽度可以通过 useWindowSize() 响应式获取。


实现

1、通过 vueuse 的 useWindowSize() 方法响应式获取设备宽度

image-20250312170341639

2、响应式设置 van-swipe 的宽度

image-20250312170229174


扩展:使用原生的方式实现

image-20250312170703569

关注医生

image-20250312171926672

image-20250312171947387

接口
  • URL/like

  • 类型POST

  • token:携带

  • 参数

    ts
    {
        type: string // topic: 百科话题, knowledge: 百科文章, doc: 医生, disease: 疾病
        id: string // 对应的id
    }
  • 返回数据

    image-20250312172159291

TS类型

image-20250312173019615

实现关注

1、在 services/home.ts 中发送网络请求

image-20250312173755304

2、在组件中,当点击关注/取消关注按钮时,调用请求方法实现关注/取消关注功能

image-20250312174146105

image-20250312174107047

关注文章
封装关注逻辑@

image-20250312174322125

image-20250312174307251

1、将关注逻辑封装到 hooks/index.ts 中

image-20250312174629323

2、在 doctor-card 组件中导入并使用封装的hook

image-20250312174744655

image-20250312174146105

实现关注文章

1、修改封装的 useFollow() 方法,添加type参数,修改item类型

image-20250312175437887

2、在 knowledge-card 组件中,使用 useFollow() 方法,实现关注文章

image-20250312175558063

image-20250312175607252

Consult

image-20250312175823339

image-20250312175839658

image-20250312180136818

TS类型

知识点Enum

1、使用枚举类型定义问诊类型、问诊时间

ts
// 问诊类型
export enum ConsultType {
  /** 找医生 */
  Doctor = 1,
  /** 快速问诊 */
  Fast = 2,
  /** 开药问诊 */
  Medication = 3
}
// 问诊时间,以1自增可以省略
export enum IllnessTime {
  /** 一周内 */
  Week = 1,
  /** 一月内 */
  Month,
  /** 半年内 */
  HalfYear,
  /** 半年以上 */
  More
}

2、定义问诊记录类型

ts
import { ConsultType, IllnessTime } from '@/enums'

// 图片列表
export type Image = {
  /** 图片ID */
  id: string
  /** 图片地址 */
  url: string
}
// 问诊记录
export type Consult = {
  /** 问诊记录ID */
  id: string
  /** 问诊类型 */
  type: ConsultType
  /** 快速问诊类型,0 普通 1 三甲 */
  illnessType: 0 | 1
  /** 科室ID */
  depId: string
  /** 疾病描述 */
  illnessDesc: string
  /** 疾病持续时间 */
  illnessTime: IllnessTime
  /** 是否就诊过,0 未就诊过  1 就诊过 */
  consultFlag: 0 | 1
  /** 图片数组 */
  pictures: Image[]
  /** 患者ID */
  patientId: string
  /** 优惠券ID */
  couponId: string
}

// 问诊记录-全部可选
export type PartialConsult = Partial<Consult>
// Required 转换为全部必须   Partial 转换问全部可选  两个内置的泛型类型